/*
 * Copyright (C) 2011, EADS France
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 */

package xowl.generator;

/**
 *
 * @author Laurent WOUTERS
 */
public class ClassGenerator {
    private static int counter = 0;
    
    protected PackageGenerator packageGenerator;
    protected xowl.lang.runtime.Class _class;
    protected String className;
    protected java.util.List<ClassGenerator> equivalents;
    protected java.util.List<ClassGenerator> superClasses;
    protected java.util.List<ClassGenerator> subClasses;
    protected java.util.List<PropertyGenerator> properties;
    protected java.util.List<PropertyInterface> propertyInterfaces;
    protected java.util.Map<PropertyGenerator, PropertyImplementation> propertyImplementations;
    protected java.util.List<InstanceGenerator> staticInstances;

    public PackageGenerator getPackage() { return packageGenerator; }
    public xowl.lang.runtime.Class getOWLClass() { return _class; }
    public java.util.Collection<ClassGenerator> getEquivalents() { return equivalents; }
    public java.util.Collection<ClassGenerator> getEquivalencyGroup() {
        java.util.List<ClassGenerator> group = new java.util.ArrayList<ClassGenerator>(equivalents);
        group.add(this);
        return group;
    }
    public java.util.Collection<ClassGenerator> getDirectSuperClasses() { return superClasses; }
    public java.util.Collection<ClassGenerator> getSuperClasses() {
        java.util.List<ClassGenerator> ancestors = new java.util.ArrayList<ClassGenerator>(superClasses);
        for (int i=0; i!=ancestors.size(); i++) {
            for (ClassGenerator ancestor : ancestors.get(i).superClasses) {
                if (!ancestors.contains(ancestor))
                    ancestors.add(ancestor);
            }
        }
        return ancestors;
    }
    public java.util.Collection<ClassGenerator> getDirectSubClasses() { return subClasses; }
    public java.util.Collection<ClassGenerator> getSubClasses() {
        java.util.List<ClassGenerator> descendants = new java.util.ArrayList<ClassGenerator>(subClasses);
        for (int i=0; i!=descendants.size(); i++) {
            for (ClassGenerator descendant : descendants.get(i).subClasses) {
                if (!descendants.contains(descendant))
                    descendants.add(descendant);
            }
        }
        return descendants;
    }
    public java.util.Collection<PropertyGenerator> getProperties() { return properties; }
    public java.util.Collection<PropertyInterface> getPropertyInterfaces() { return propertyInterfaces; }
    public java.util.Collection<PropertyImplementation> getPropertyImplementations() { return propertyImplementations.values(); }
    public PropertyImplementation getPropertyImplementation(xowl.lang.runtime.Property property) {
        for (PropertyGenerator gen : propertyImplementations.keySet())
            if (gen.getOWLProperty() == property)
                return propertyImplementations.get(gen);
        return null;
    }
    public PropertyImplementation getPropertyImplementation(PropertyGenerator gen) { return propertyImplementations.get(gen); }
    public java.util.Collection<InstanceGenerator> getStaticInstances() { return staticInstances; }
    public InstanceGenerator getStaticInstance(xowl.lang.runtime.NamedIndividual ind) {
        for (InstanceGenerator inst : staticInstances)
            if (inst.getOWLInstance() == ind)
                return inst;
        return null;
    }
    public String getName() { return className; }
    public String getJavaName() { return packageGenerator.getFullName() + "." + className; }
    public String getPrefixedName() { return packageGenerator.getName() + "_" + className; }
    public boolean isAnonymous() { return (_class.getInterpretationOf() == null); }
    public boolean isAbstract() { return (!subClasses.isEmpty()); }
    public boolean isEnumeration() { return (!_class.getAllClassOneOf().isEmpty()); }
    public boolean isCompatibleWith(ClassGenerator c) { return ((c == this) || this.getSuperClasses().contains(c)); }
    public void addInstance(InstanceGenerator instance) { staticInstances.add(instance); }
    public void addProperty(PropertyGenerator property) { properties.add(property); }

    public ClassGenerator getClassGenerator(xowl.lang.runtime.Class c) { return packageGenerator.getModel().getClassGenerator(c); }
    public PropertyGenerator getPropertyGenerator(xowl.lang.runtime.Property prop) { return packageGenerator.getModel().getPropertyGenerator(prop); }

    
    
    public ClassGenerator(Application app, PackageGenerator pack, xowl.lang.runtime.Class c) {
        packageGenerator = pack;
        _class = c;
        equivalents = new java.util.ArrayList<ClassGenerator>();
        superClasses = new java.util.ArrayList<ClassGenerator>();
        subClasses = new java.util.ArrayList<ClassGenerator>();
        properties = new java.util.ArrayList<PropertyGenerator>();
        propertyInterfaces = new java.util.ArrayList<PropertyInterface>();
        propertyImplementations = new java.util.HashMap<PropertyGenerator, PropertyImplementation>();
        staticInstances = new java.util.ArrayList<InstanceGenerator>();
        if (_class.getInterpretationOf() == null) {
            if (!_class.getAllClassUnionOf().isEmpty())
                buildUnionName(app);
            else if (!_class.getAllClassIntersectionOf().isEmpty())
                buildIntersectionName(app);
        } else {
            String iri = _class.getInterpretationOf().getHasIRI().getHasValue();
            String[] parts = iri.split("#");
            className = parts[parts.length-1];
            for (xowl.lang.runtime.Individual ind : _class.getAllClassifies()) {
                if (ind instanceof xowl.lang.runtime.NamedIndividual) {
                    InstanceGenerator instGen = new InstanceGenerator(this, (xowl.lang.runtime.NamedIndividual)ind);
                    pack.getModel().addInstanceGenerator((xowl.lang.runtime.NamedIndividual)ind, instGen);
                    staticInstances.add(instGen);
                }
            }
        }
    }
    private void buildUnionName(Application app) {
        java.lang.StringBuilder builder = new java.lang.StringBuilder();
        java.util.List<String> names = new java.util.ArrayList<String>();
        for (xowl.lang.runtime.Class member : _class.getAllClassUnionOf()) {
            ClassGenerator gen = getClassGenerator(member);
            if (gen == null)
                continue;
            names.add(gen.className);
        }
        java.util.Collections.sort(names);
        for (String name : names) {
            if (builder.length() != 0)
                builder.append("_OR_");
            builder.append(name);
        }
        className = builder.toString();
        if (className.length() > app.getMaxFileLength()) {
            className = className.substring(0, app.getMaxFileLength() - 1) + Integer.toString(counter);
            counter++;
        }
    }
    private void buildIntersectionName(Application app) {
        java.lang.StringBuilder builder = new java.lang.StringBuilder();
        java.util.List<String> names = new java.util.ArrayList<String>();
        for (xowl.lang.runtime.Class member : _class.getAllClassIntersectionOf()) {
            ClassGenerator gen = getClassGenerator(member);
            if (gen == null)
                continue;
            names.add(gen.className);
        }
        java.util.Collections.sort(names);
        for (String name : names) {
            if (builder.length() != 0)
                builder.append("_AND_");
            builder.append(name);
        }
        className = builder.toString();
        if (className.length() > app.getMaxFileLength()) {
            className = className.substring(0, app.getMaxFileLength() - 1) + Integer.toString(counter);
            counter++;
        }
    }

    public void buildEquivalents() {
        for (xowl.lang.runtime.Class eq : _class.getAllClassEquivalentTo()) {
            ClassGenerator gen = getClassGenerator(eq);
            if (gen != null && !equivalents.contains(gen)) {
                equivalents.add(gen);
                gen.equivalents.add(this);
            }
        }
    }
    public void buildRestrictions() {
        for (xowl.lang.runtime.ClassRestriction restriction : _class.getAllClassRestrictions()) {
            if (restriction instanceof xowl.lang.runtime.ObjectAllValuesFrom) {
                xowl.lang.runtime.ObjectAllValuesFrom r = (xowl.lang.runtime.ObjectAllValuesFrom)restriction;
                getClassGenerator((xowl.lang.runtime.Class)r.getClasse());
            } else if (restriction instanceof xowl.lang.runtime.ObjectSomeValuesFrom) {
                xowl.lang.runtime.ObjectSomeValuesFrom r = (xowl.lang.runtime.ObjectSomeValuesFrom)restriction;
                getClassGenerator((xowl.lang.runtime.Class)r.getClasse());
            }
        }
    }
    public void buildHierarchy() {
        for (xowl.lang.runtime.Class parent : _class.getAllSubClassOf()) {
            ClassGenerator gen = getClassGenerator(parent);
            if (gen != null && !gen.className.equals("Thing") && !superClasses.contains(gen)) {
                superClasses.add(gen);
                gen.subClasses.add(this);
            }
        }
        for (xowl.lang.runtime.Class operand : _class.getAllClassUnionOf()) {
            ClassGenerator gen = getClassGenerator(operand);
            if (gen != null && !subClasses.contains(gen)) {
                subClasses.add(gen);
                gen.superClasses.add(this);
            }
        }
        for (xowl.lang.runtime.Class operand : _class.getAllClassIntersectionOf()) {
            ClassGenerator gen = getClassGenerator(operand);
            if (gen != null && !superClasses.contains(gen)) {
                superClasses.add(gen);
                gen.subClasses.add(this);
            }
        }
    }
    public void buildHierarchy_Union() {
        if (_class.getAllClassUnionOf().isEmpty())
            return;
        java.util.List<java.util.Collection<ClassGenerator>> ancestors = new java.util.ArrayList<java.util.Collection<ClassGenerator>>();
        for (xowl.lang.runtime.Class operand : _class.getAllClassUnionOf()) {
            ClassGenerator gen = getClassGenerator(operand);
            if (gen != null)
                ancestors.add(getClassGenerator(operand).getSubClasses());
        }
        java.util.Collection<ClassGenerator> intersec = ancestors.get(0);
        for (int i=1; i!=ancestors.size(); i++)
            intersec = intersection(intersec, ancestors.get(i));
        for (ClassGenerator ancestor : intersec) {
            if (!superClasses.contains(ancestor)) {
                superClasses.add(ancestor);
                ancestor.subClasses.add(this);
            }
        }
    }
    private static java.util.List<ClassGenerator> intersection(java.util.Collection<ClassGenerator> l1, java.util.Collection<ClassGenerator> l2) {
        java.util.List<ClassGenerator> result = new java.util.ArrayList<ClassGenerator>();
        for (ClassGenerator e : l1)
            if (l2.contains(e))
                result.add(e);
        return result;
    }

    public void buildInterfaces() {
        java.util.List<PropertyGenerator> inheritedProperties = new java.util.ArrayList<PropertyGenerator>();
        for (ClassGenerator equivalent : getEquivalencyGroup()) {
            for (ClassGenerator ancestor : equivalent.getSuperClasses()) {
                for (PropertyGenerator property : ancestor.getProperties()) {
                    java.util.List<PropertyGenerator> tree = buildInterfaces_GetAllProperties(property);
                    for (PropertyGenerator sub : tree)
                        if (!inheritedProperties.contains(sub))
                            inheritedProperties.add(sub);
                }
            }
        }
        java.util.List<PropertyGenerator> myProperties = new java.util.ArrayList<PropertyGenerator>();
        for (ClassGenerator equivalent : getEquivalencyGroup()) {
            for (PropertyGenerator property : equivalent.getProperties()) {
                if (inheritedProperties.contains(property))
                    continue;
                // New Property
                java.util.List<PropertyGenerator> tree = buildInterfaces_GetAllProperties(property);
                for (PropertyGenerator sub : tree) {
                    if (!inheritedProperties.contains(sub)) {
                        propertyInterfaces.add(new PropertyInterface(this, sub));
                        myProperties.add(sub);
                    }
                }
            }
        }
        for (ClassGenerator equivalent : getEquivalencyGroup()) {
            for (xowl.lang.runtime.ClassRestriction restriction : equivalent._class.getAllClassRestrictions()) {
                PropertyGenerator property = getPropertyGenerator(PropertyRepresentation.getRestrictedProperty(restriction));
                if (myProperties.contains(property))
                    continue;
                // Restriction does not apply on a property for which this class is the domain
                if (!inheritedProperties.contains(property))
                    continue;
                // Restriction applies on an inherited property
                java.util.List<PropertyGenerator> tree = buildInterfaces_GetAllProperties(property);
                for (PropertyGenerator sub : tree) {
                    if (!myProperties.contains(sub)) {
                        propertyInterfaces.add(new PropertyInterface(this, sub));
                        myProperties.add(sub);
                    }
                }
            }
        }
    }
    private java.util.List<PropertyGenerator> buildInterfaces_GetAllProperties(PropertyGenerator property) {
        java.util.List<PropertyGenerator> result = new java.util.ArrayList<PropertyGenerator>();
        ClassGenerator domain = property.getDomain();
        result.add(property);
        for (int i=0; i!=result.size(); i++)
            for (PropertyGenerator sub : result.get(i).getSubProperties())
                if (domain.getEquivalencyGroup().contains(sub.getDomain()))
                    result.add(sub);
        return result;
    }

    public void buildImplementations() {
        if (isAbstract()) return;
        java.util.Map<PropertyGenerator, java.util.List<PropertyInterface>> interfaces = new java.util.HashMap<PropertyGenerator, java.util.List<PropertyInterface>>();
        for (ClassGenerator equivalent : getEquivalencyGroup()) {
            for (ClassGenerator ancestor : equivalent.getSuperClasses()) {
                for (PropertyInterface inter : ancestor.getPropertyInterfaces()) {
                    if (!interfaces.containsKey(inter.getProperty()))
                        interfaces.put(inter.getProperty(), new java.util.ArrayList<PropertyInterface>());
                    interfaces.get(inter.getProperty()).add(inter);
                }
            }
            for (PropertyInterface inter : equivalent.getPropertyInterfaces()) {
                if (!interfaces.containsKey(inter.getProperty()))
                    interfaces.put(inter.getProperty(), new java.util.ArrayList<PropertyInterface>());
                interfaces.get(inter.getProperty()).add(inter);
            }
        }


        for (PropertyGenerator property : interfaces.keySet()) {
            PropertyImplementation implementation = new PropertyImplementation(this, property);
            propertyImplementations.put(property, implementation);
            for (PropertyInterface inter : interfaces.get(property))
                implementation.addInterface(inter);
            implementation.close();
        }
    }

    public void write(String folder, GenerationMode mode, int signature) throws java.io.IOException {
        if (mode == GenerationMode.Native)
            xowl.generator.standalone.ClassWriter.write(folder, this);
        else if (mode == GenerationMode.Proxy)
            xowl.generator.proxy.ClassWriter.write(folder, this, signature);
    }
}
